////////////////////////////////////////////////
// DESCRIPTION:
//     Top level stream driver Exported APIs
//
// Legal Notices:
//     Copyright (c) 2008, Telliam Consulting, LLC.
//     All rights reserved.
//
//     Redistribution and use in source and binary forms, with or without modification,
//     are permitted provided that the following conditions are met:
//
//     * Redistributions of source code must retain the above copyright notice,
//       this list of conditions and the following disclaimer.
//
//     * Redistributions in binary form must reproduce the above copyright notice,
//       this list of conditions and the following disclaimer in the documentation
//       and/or other materials provided with the distribution.
//
//     * Neither the name of Telliam Consulting nor the names of its contributors
//       may be used to endorse or promote products derived from this software
//       without specific prior written permission.
//
// Disclaimer:
//     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
//     ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
//     WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
//     IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
//     INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
//     BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
//     DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
//     LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
//     OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
//     OF THE POSSIBILITY OF SUCH DAMAGE.  
//

// Required Entry Points for Stream Device Driver DLLs 
//
// The required entry points in any stream device driver DLL are
// those which implement the standard Win32 file I/O functions, plus
// the power management functions used by the Windows CE kernel. The
// entry points for installable drivers are
//
//    BOOL  XXX_Close(DWORD hOpenContext); 
//    DWORD XXX_Init(DWORD dwContext);  
//    DWORD XXX_Open(DWORD hDeviceContext, DWORD AccessCode, DWORD ShareMode); 
//    void  XXX_PowerUp(DWORD hDeviceContext); 
//    DWORD XXX_Seek(DWORD hOpenContext, long Amount,DWORD Type); 
//    BOOL  XXX_Deinit(DWORD hDeviceContext); 
//    BOOL  XXX_IOControl(DWORD hOpenContext,DWORD dwCode,PBYTE pBufIn,
//                        DWORD dwLenIn,PBYTE pBufOut,DWORD dwLenOut,PDWORD pdwActualOut); 
//    void  XXX_PowerDown(DWORD hDeviceContext);
//    DWORD XXX_Read(DWORD hOpenContext,LPVOID pBuffer,DWORD Count); 
//    DWORD XXX_Write(DWORD hOpenContext,LPCVOID pSourceBytes,DWORD NumberOfBytes); 
//
// For details about these entry points, see "Stream I/O Functions and
// Required Entry Points"
//
// Additionally a standard Win32 DLL Entry Point may be provided as follows:
//    BOOL DllMain(HINSTANCE hinstDLL,DWORD fdwReason,LPVOID lpvReserved)
//
// Interrupt Processing in Stream Device Drivers 
// Some peripheral devices are capable of causing or signaling interrupts
// on the Windows CE platform's bus, Typically such peripherals are PC
// Cards, but not exclusively. Stream drivers for such
// peripherals therefore need code to process such interrupt events.
//
// The recommended method is for the stream device driver to spawn a
// new thread called an Interrupt Service Thread (IST) during its
// processing of the XXX_Init() call. ISTs typically run at a high
// priority, so that they can respond quickly to interrupt events.
// Depending on the type of driver you are writing, this thread should
// register to receive interrupt notifications by means of the
// InterruptInitialize() function, or CardRequestIRQ() for PC Card drivers.
// PC Card drivers do not process interrupts directly, since the
// built-in PC Card socket driver gets the raw interrupts generated
// by the PC Card socket. CardRequestIRQ() allows you to specify a callback
// function that the PC Card socket driver will call when an interrupt
// occurs,
//
// When the appropriate interrupt occurs, Windows CE will signal your
// IST. Your IST can then interact with your peripheral device to perform
// whatever processing is necessary to handle the interrupt.
//
// NOTE:
//    As of V4.x Drivers do not need to include the prefix
//    in the device API exports. Using this feature requires
//    setting DEVLOAD_NAKEDENTRIES in the DEVLOAD_FLAGS_VALNAME
//    setting in the registry for the device driver. This
//    implementation takes advantage of that and exports the
//    naked stream driver interfaces using __declspec(dllexport) 
//    so no DEF file is required.
//
//    As of V5.0 the kernel uses a separate high priority thread for
//    calling the driver power down code so that it is safe to use
//    Critical sections and Mutex synch objects and other APIs in powerdown.
//

#include <windows.h>
#include <FusionWare\DFX\StreamAPI.h>

using namespace FusionWare::DFX;

// map of all Driver context instances loaded
DriverContextMap g_DriverContextMap;

// map of all open contexts used for this driver
OpenContextMap g_OpenContextMap;

//ALL Driver APIs are exported using C Linkage
extern "C" {
    /////////////////////////////////////////////////
    //Description:
    //   Initialize the device
    //
    // Input:
    //  dwContext - Specifies a pointer to a string containing the registry path to 
    //              the active key for the installable device driver. 
    //Returns:
    // Returns a handle to the device context created. This handle is passed
    // to the XXX_Open(), XXX_PowerDown(), XXX_PowerUp(), and XXX_Deinit() functions. 
    //
    //Remarks:
    // When the user starts using a device, such as when a PC Card is inserted,
    // the Device Manager calls this function to initialize the device. This function
    // is not called directly by applications. 
    // The Device Manager specifies a pointer to a string containing the registry path
    // to the active key of the specific PC Card device in the dwContext parameter.
    // Usually the string contains the registry path to a numbered subkey of the
    // HKEY_LOCAL_MACHINE\Drivers\Active key. Your initialization function can use this
    // information to access data stored in the registry. 
    //
    // Some tasks this function might perform include: 
    // Initialize the installed device to a default state. 
    //  * Allocate resources used globally by the device driver. 
    //  * Register a status callback function with Card services. When the status of the
    //    device changes, the operating system calls this status callback function. For
    //    example, the status of a PC Card changes when you insert it or remove it. 
    //  * Map system memory and I/O space to a PC Card device's memory and I/O space. 
    //  * Request notification on specific callback events. 
    //  * For an interrupt-driven device, register an interrupt callback function. When
    //    the device generates an interrupt, the operating system calls this interrupt
    //    callback function.
    // After this function returns, the Device Manager checks the registry for a key
    // named "Ioctl" for the driver, and if such a key is present Device Manger calls
    // XXX_IOControl(). Your driver can use this function to finish initializing itself
    // after it has been installed. For example, a driver might load additional modules
    // that require the underlying driver to be installed before they can load. 
    //
    DWORD Init(DWORD dwContext)
    {
        DWORD retVal = 0;

        // Instantiate a new driver object
        IDriverContextPtr pDrv;
        
        if(!CreateDriverContext(dwContext, &pDrv))
        {
            SetLastError(ERROR_NOT_ENOUGH_MEMORY);
            return 0;
        }
    
        // call the objects Init method to do the real work
        if(!pDrv->Init((const TCHAR*)dwContext))
            return 0;
        
        // safely add the new instance to the map and get it's "handle"
        return g_DriverContextMap.Add(pDrv);
    }

    /////////////////////////////////////////////////
    //Description:
    //  Device Manager calls xxx_PreDeinit() function to mark the device as "dead". 
    //
    //Input:
    //  hDeviceContext - Specifies a handle used to identify the device context. 
    //                   The xxx_Init() function creates and returns this identifier. 
    //Returns:
    //  TRUE if the device successfully marked, otherwise it returns FALSE. 
    //
    //Remarks:
    //  This function marks a driver context as no longer valid. Device Manager calls this
    //  before calling DeInit then waits until all threads into the driver have left the
    //  driver while blocking new ones from entering. It then calls DeInit to finalize the
    //  driver shutdown. This prevents a class of race conditions where Device manager has
    //  called DeInit as an application is calling CreateFile()
    //
    BOOL PreDeinit(DWORD hDeviceContext)
    {
        // Get ValidContext will call SetLastError on failure
        IDriverContextPtr pDrv = g_DriverContextMap.GetValidContext(hDeviceContext);
        if(!pDrv) 
            return FALSE;

        return pDrv->PreDeinit();
    }


    /////////////////////////////////////////////////
    //Description:
    //  Device Manager calls xxx_Deinit() function to de-initialize your device. 
    //
    //Input:
    //  hDeviceContext - Specifies a handle used to identify the device context. 
    //                   The xxx_Init() function creates and returns this identifier. 
    //Returns:
    //  TRUE if the device successfully de-initialized, otherwise it returns FALSE. 
    //
    //Remarks:
    //  When the user stops using a device, such as when a PC Card is removed from its
    //  socket, Device Manager calls this function. This function is not called by
    //  applications. Your installable device driver should free any resources it has
    //  allocated and terminate. 
    //
    BOOL Deinit(DWORD hDeviceContext)
    {
        return g_DriverContextMap.Remove(hDeviceContext);
    }

    /////////////////////////////////////////////////
    //Description:
    //  Opens a device for reading and/or writing. An application indirectly invokes
    //  this function when it calls CreateFile to open special Device Filenames. 
    //
    //Input:
    // hDeviceContext - Specifies a handle identifying the device context. The
    //                  XXX_Init() function creates and returns this handle. 
    // AccessCode     - Specifies the requested access code of the device. The access is a combination of
    //                  read and write. 
    // ShareMode      - Specifies the requested file share mode of the device. The share mode is a combination
    //                  of file read and write sharing. 
    //Returns:
    //  This function returns a handle that identifies the open context of the device to the
    //  calling application. If your device can be opened multiple times, use this handle to
    //  identify each open context. This identifier is passed into the XXX_Read(),
    //  XXX_Write(), XXX_Seek(), and XXX_IOControl() functions. If the device cannot be
    //  opened, the function returns NULL. 
    //
    //Remarks:
    // When this function is called, your device should allocate resources it needs for each
    // open context and prepare for operation. This might involve preparing the device for
    // reading or writing, and initializing data structures it uses for operation.
    //
    DWORD Open(DWORD hDeviceContext, DWORD AccessCode, DWORD ShareMode)
    {
        // Get ValidContext will call SetLastError on failure
        IDriverContextPtr pDrv = g_DriverContextMap.GetValidContext(hDeviceContext);
        if(!pDrv) 
            return 0;
        
        IOpenContextPtr pOpen;
        if(!pDrv->Open(AccessCode, ShareMode, &pOpen))
            return 0;

        return g_OpenContextMap.Add(pOpen);
    }

    /////////////////////////////////////////////////
    //Description:
    //  Device Manager calls xxx_PreClose() function to mark
    //  the Open context device as "dead". 
    //
    //Input:
    //  hOpenContext - Specifies a handle used to identify the open context. 
    //                 The xxx_Open() function creates and returns this identifier. 
    //Returns:
    //  TRUE if the device successfully marked, otherwise it returns FALSE. 
    //
    //Remarks:
    //  This function marks a Open context as no longer valid. Device Manager calls this
    //  before calling xxx_Close() then waits until all threads into context have left the
    //  context while blocking new ones from entering. It then calls Close to finalize the
    //  context shutdown. This prevents a class of race conditions where Device manager has
    //  called Closed on one thread but the application is calling IoControl on another one.
    //
    BOOL PreClose(DWORD hOpenContext)
    {
        // Get ValidContext will call SetLastError on failure
        IOpenContextPtr pOpen = g_OpenContextMap.GetValidContext(hOpenContext);
        if(!pOpen) 
            return FALSE;

        return pOpen->PreClose();
    }

    /////////////////////////////////////////////////
    //Description:
    //  Closes the device context identified by hOpenContext. 
    //
    //Input:
    //  hOpenContext - The handle returned by XXX_Open() used to identify the
    //                 open context of the device. 
    //Return:
    //  TRUE if the device successfully closed, and FALSE otherwise. 
    //
    //Remarks:
    //  An application calls CloseHandle(hFile) to cease using an installable device.
    //  The hFile argument specifies the handle associated with the device context. In
    //  response to CloseHandle(), the operating system invokes XXX_Close(). 
    //  The file handle specified for hOpenContext should be invalid after this function
    //  returns, such that if an application tries to perform Stream I/O functions on that
    //  handle after calling CloseHandle() an error occurs.
    //
    BOOL Close(DWORD hOpenContext)
    {
        return g_OpenContextMap.Remove(hOpenContext);
    }

    /////////////////////////////////////////////////
    //Description:
    //  Sends a command to the device. 
    //
    //Input:
    //  hOpenContext - Specifies a handle identifying the open context of the device.
    //                 The xxx_Open() function creates and returns this identifier. 
    //  dwCode       - Specifies a value indicating the I/O control operation to perform.
    //                 These codes are device specific, and are usually exposed to application
    //                 programmers by means of a header file. 
    //  pBufIn       - Points to the buffer containing data to be transferred to the device. 
    //  dwLenIn      - Specifies the number of bytes of data in the buffer specified for pBufIn. 
    //  pBufOut      - Points to the buffer used to transfer the output data from the device. 
    //  dwLenOut     - Specifies the maximum number of bytes in the buffer specified by pBufOut
    //  pdwActualOut - Points to DWORD buffer the function uses to return the actual number of
    //                 bytes received from the device. 
    //Returns:
    //  Returns TRUE if the device successfully completed its specified I/O control operation,
    //  otherwise it returns FALSE. 
    //
    //Remarks:
    //  An application uses DeviceIOControl() to specify an operation to be performed. The 
    //  operating system, in turn, invokes xxx_IOControl(). The dwCode argument contains
    //  the input or output operation to be performed. I/O control codes are usually specific
    //  to each device driver, and are typically exposes to application programmers by means
    //  of a header file. 
    //  If the "Ioctl" registry value is defined for your device driver, the Device Manager
    //  calls xxx_IOControl() right after it calls xxx_Init(). It uses the value stored
    //  in the registry for the dwCode argument, and NULL for the pBufIn and pBufOut
    //  arguments. Your device might use this option to load other modules that require the
    //  basic device driver to be installed. 
    //
    BOOL IOControl(DWORD hOpenContext, DWORD IoCode, BYTE* pInBuf, DWORD InBufSize, BYTE* pOutBuf, DWORD OutBufSize, DWORD* pActualOut)
    {   
        bool retVal=FALSE;
        
        IOpenContextPtr pOpen = g_OpenContextMap.GetValidContext(hOpenContext);
        if(!pOpen)
            return FALSE;

        if(pOpen->IoControl( IoCode, pInBuf, InBufSize, pOutBuf, OutBufSize, pActualOut, &retVal))
            return retVal;

        SetLastError(ERROR_INVALID_FUNCTION);
        return (BOOL)retVal;
    }

    /////////////////////////////////////////////////
    //Description:
    // Reads data from the device identified by the open context. 
    //
    //Input:
    //  hOpenContext - Specifies a handle that identifies the open context
    //                 of the device. The XXX_Open() function creates and
    //                 returns this identifier. 
    //  pBuffer      - Points to the buffer which stores the data read from
    //                 the device. This buffer should be at least Count bytes long. 
    //  Count        - Specifies the number of bytes to read from the device into pBuffer. 
    //
    //Returns:
    //  Returns 0 for the end-of-file, -1 for an error, or the number of bytes read
    //  for success. 
    //
    //Remarks:
    //  An application calls ReadFile() to read from the device. The operating system,
    //  in turn, invokes XXX_Read(). The hFile argument is a handle for your device.
    //  The pData argument points to the buffer that contains the data read from the device.
    //  The Size argument indicates the number of bytes the application wants to read from
    //  the device. The pSizeRead argument is the address of a value where XXX_Read()
    //  can store the number of bytes actually read from the device. The value returned from
    //  XXX_Read() is equal to the value contained in pSizeRead, unless XXX_Read()
    //  returns -1 for an error. If XXX_Read() returns an error, then pSizeRead will
    //  contain 0. 
    //
    DWORD Read(DWORD hOpenContext, void* pBuffer, DWORD Count)
    {
        IOpenContextPtr pOpen = g_OpenContextMap.GetValidContext(hOpenContext);
        if(!pOpen)
            return -1;

        return pOpen->Read(pBuffer, Count);
    }


    /////////////////////////////////////////////////
    //Description:
    //  Writes data to the device. 
    // 
    //Input:
    //  hOpenContext  - Specifies a handle that identifies the open context of the
    //                  device. The xxx_Open() call returns this identifier. 
    //  pSourceBytes  - Specifies the address of the buffer that contains the data to write. 
    //  NumberOfBytes - Specifies the number of bytes to write from the pSourceBytes
    //                  buffer into the device. 
    //
    //Returns:
    //  Returns -1 for an error, or the number of bytes written for success. 
    //
    //Remarks:
    //  An application uses WriteFile() to write to the device. The operating system, in
    //  turn, invokes xxx_Write(). The value returned from xxx_Write() is equal to the
    //  value contained in NumberOfBytes, unless xxx_Write() returns -1 for an error. 
    //
    DWORD Write(DWORD hOpenContext, const void* pSourceBytes, DWORD NumberOfBytes)
    {
        IOpenContextPtr pOpen = g_OpenContextMap.GetValidContext(hOpenContext);
        if(!pOpen)
            return -1;

        return pOpen->Write(pSourceBytes, NumberOfBytes);
    }

    /////////////////////////////////////////////////
    //Description:
    //  The xxx_PowerUp() function restores power to a device. 
    //
    //Input:
    //  hOpenContext - Specifies a handle that identifies the device context. The
    //                 xxx_Init() call returns this identifier. 
    //
    //Remarks:
    //  The operating system invokes this function to restore power to a device. The operating
    //  system might call this function as it is leaving its power saving mode. This function
    //  should not call any functions that may cause it to block and should return as quickly
    //  as possible. This function should set a global variable to indicate that power was
    //  restored and perform any necessary processing later. 
    //
    //Note:
    //  Some Windows CE platforms provide a subset of the specifications for PC Card power.
    //  If you are writing a driver for a PC Card device, consult with the platform's
    //  manufacturer to ensure that it can support your PC Card.
    //
    void PowerUp(DWORD hDeviceContext)
    {
        IDriverContextPtr pDrv = g_DriverContextMap.GetValidContext(hDeviceContext);
        if(pDrv!=NULL)
            pDrv->PowerUp();
    }

    /////////////////////////////////////////////////
    //Description:
    //  The xxx_PowerDown() function suspends power to the device. This is only useful with 
    //  devices that can be shut off under software control. Such devices are typically PC
    //  Card devices, but not exclusively so. 
    //
    //Input:
    //  Specifies a handle that identifies the device context. The xxx_Init() call returns
    //  this identifier. 
    //
    //Remarks:
    //  The operating system invokes this function to suspend power to the device. The 
    //  operating system might call this function when it is about to enter the power saving 
    //  mode. This function should never call any functions that might cause it to block and 
    //  it should return as quickly as possible. One strategy for returning quickly is to have
    //  this function set a global variable to indicate that a power loss occurred and perform 
    //  any necessary processing later. 
    //
    void PowerDown(DWORD hDeviceContext)
    {
        IDriverContextPtr pDrv = g_DriverContextMap.GetValidContext(hDeviceContext);
        if(pDrv!=NULL)
            pDrv->PowerDown();
    }


    /////////////////////////////////////////////////
    //Description:
    //  Moves the data pointer in the device. 
    //
    //Input:
    //  hOpenContext - Specifies a handle that identifies the open context of the device.
    //                 The xxx_Open() call returns this identifier. 
    //  Amount       - Specifies the number of bytes to move the data pointer in the device.
    //                 A positive value moves the data pointer towards the end of the file
    //                 and a negative value moves it towards the beginning. 
    //  Type         - Specifies the starting point for the data pointer: 
    //    FILE_BEGIN     Indicates the starting point is zero or the beginning of the file. 
    //    FILE_CURRENT   Indicates the current value of the file pointer is the starting point. 
    //    FILE_END       Indicates the current end-of-file position is the starting point. 
    //
    //Returns:
    //  Returns -1 for an error, or the device's new data pointer for success. 
    //
    //Remarks:
    //  An application calls SetFilePointer to move the data pointer in the device. The 
    //  operating system, in turn, invokes xxx_Seek(). If your device is capable of being
    //  opened multiple times, xxx_Seek() should only modify the data pointer for the
    //  instance specified by hOpenContext.
    // 
    DWORD Seek(DWORD hOpenContext, long Amount, DWORD Type)
    {
        IOpenContextPtr pOpen = g_OpenContextMap.GetValidContext(hOpenContext);
        if(!pOpen)
            return -1;

        return pOpen->Seek(Amount, Type);
    }

} // EXTERN "C"

